﻿using System;
using System.Collections.Generic;
using System.Text;


namespace SscLib
{
    /// <summary>
    /// Abstract base class for symbols containing (defining) other symbols.
    /// Such symbols defines a new Scope, in addition of being a Symbol by itself.
    /// </summary>
    public abstract class ScopeSymbol : Symbol
    {

        /// <summary>
        /// Constructor for ScopeSymbol.
        /// </summary>
        /// <param name="name">Symbol name.</param>
        /// <param name="type">Symbol type.</param>
        /// <param name="scope">Containing Scope.</param>
        public ScopeSymbol(string name, TypeSymbol type) : base(name,type)
        {
        }

        /// <summary>Returns a Dictionary containing Symbols defined inside this Scope.</summary>
        public abstract Dictionary<string, Symbol> Members { get; }

        /// <summary>
        /// Adds new member to this scope.
        /// </summary>
        /// <param name="name">Name of the symbol to add to this scope.</param>
        /// <param name="sym">The Symbol to add to this scope.</param>
        /// <exception cref="System.ArgumentException">Thrown when a symbol with the same name allready exists in this scope.</exception>
        public virtual void Add(string name, Symbol sym)
        {
            bool exists = this.Members.ContainsKey(name);
            if (exists) throw new ArgumentException("An Item with name " + sym.Name + " in scope " + this.Name + " allready exists.", "name");
            this.Members.Add(name, sym);
            sym.Scope = this;
        }

        /// <summary>
        /// Resolves the symbol name in this scope.
        /// If the symbol is not defined in this scope, then tries to resolve in the scope enclosing this scope.
        /// </summary>
        /// <param name="name">Name of the symbol to resolve.</param>
        /// <returns>The resolved symbol or null.</returns>
        public virtual Symbol Resolve(string name)
        {
            Symbol s = null;
            if (this.Members.ContainsKey(name))
            {
                s = this.Members[name];
            }
            else if (this.Scope != null)
            {
                s = this.Scope.Resolve(name);
            }

            return s;
        }

        /// <summary>
        /// Defines a new argument in the scope.
        /// The scope must be a method or constructor (MethodSymbolBase).
        /// </summary>
        /// <param name="name">Argument name.</param>
        /// <param name="type">Argument type.</param>
        /// <returns>The newly created type.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when trying to define a new argument to non-medhod or non-constructur scope.</exception>
        public virtual ArgumentSymbol AddArgument(string name, TypeSymbol type)
        {
            if (!(this is MethodSymbolBase)) throw new InvalidOperationException("Only methods can define arguments");

            ArgumentSymbol arg = new ArgumentSymbol(name, type);
            Add(name, arg);
            return arg;
        }

        /// <summary>
        /// Defines a program class in this scope.
        /// The scope must be a Module.
        /// </summary>
        /// <param name="name">Type name.</param>
        /// <param name="baseType">Inherited type.</param>
        /// <returns>The newly created program symbol.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when trying to define a new program to non-Module scope.</exception>
        public virtual ProgramSymbol AddProgram(string name, TypeSymbol baseType)
        {
            if (!(this is ModuleSymbol)) throw new InvalidOperationException("Only Modules can define Programs");

            ProgramSymbol sym = new ProgramSymbol(name, baseType);

            Add(name, sym);
            return sym;
        }

        /// <summary>
        /// Defines a new type in this scope.
        /// The scope must be a Module or a Type.
        /// </summary>
        /// <param name="name">Type name.</param>
        /// <param name="baseType">Inherited type.</param>
        /// <param name="sysType">Native .Net type, also called system type.</param>
        /// <returns>The newly created type.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when trying to define a new type to non-Module or non-Type scope.</exception>
        public virtual TypeSymbol AddType(string name, TypeSymbol baseType, Type sysType)
        {
            if (!((this is TypeSymbol) || (this is ModuleSymbol))) throw new InvalidOperationException("Only Modules and Types can define Types");

            TypeSymbol sym = new TypeSymbol(name, baseType, sysType);

            Add(name, sym);
            return sym;
        }

        /// <summary>
        /// Defines a new type in this scope.
        /// The scope must be a Module or a Type. 
        /// </summary>
        /// <param name="name">Type name.</param>
        /// <param name="baseType">Inherited type.</param>
        /// <returns>The newly created type.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when trying to define a new type to non-Module or non-Type scope.</exception>
        public virtual TypeSymbol AddType(string name, TypeSymbol baseType)
        {
            return this.AddType(name, baseType, null);
        }

        /// <summary>
        /// Defines a new constructor in the scope.
        /// The scope must be a Type.
        /// </summary>
        /// <returns>The newly defined ConstructorSymbol</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when trying to define a new constructor in a non-Type scope.</exception>
        public virtual CtorSymbol AddCtor()
        {
            if (!(this is TypeSymbol)) throw new InvalidOperationException("Only Types can define constructors.");

            CtorSymbol ctor = new CtorSymbol();
            Add(".ctor", ctor);
            return ctor;
        }

        /// <summary>
        /// Defines a new method in the scope.
        /// The scope must be a Type.
        /// </summary>
        /// <param name="name">Method name.</param>
        /// <param name="type">Method return type.</param>
        /// <returns>The newly defined MethodSymbol.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when trying to define a new method in a non-Type scope.</exception>
        public virtual MethodSymbol AddMethod(string name, TypeSymbol type)
        {
            if (!(this is TypeSymbol)) throw new InvalidOperationException("Only Types can define methods.");

            MethodSymbol meth = null;
            if (this is TypeSymbol)
            {
                meth = new MethodSymbol(name, type);
                Add(name, meth);
            }
            else meth = this.Scope.AddMethod(name, type);
            return meth;

            //TODO: Cleanup this method. The test for TypeSymbol is done twice, and only one is enough.
        }

        /// <summary>
        /// Defines a new field in the scope.
        /// The scope must be a Type.
        /// </summary>
        /// <param name="name">Field name.</param>
        /// <param name="type">Field type.</param>
        /// <returns>The newly defined FieldSymbol.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown if the scope is not a Type.</exception>
        public virtual FieldSymbol AddField(string name, TypeSymbol type)
        {
            if (!(this is TypeSymbol)) throw new InvalidOperationException("Only Types can define fields.");

            FieldSymbol f = new FieldSymbol(name, type);
            Add(name, f);
            return f;
        }

        /// <summary>
        /// Defines a new label in the scope.
        /// The scope must be a Method or Constructor.
        /// </summary>
        /// <param name="name">Label name.</param>
        /// <returns>The newly defined LabelSymbol.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown if the scope is not a Method or Constructor.</exception>
        public virtual LabelSymbol AddLabel(string name)
        {
            if (!(this is MethodSymbolBase)) throw new InvalidOperationException("Only Methods can define labels.");
            LabelSymbol l = new LabelSymbol(name);
            Add(name, l);
            return l;
        }

        /// <summary>
        /// Defines a new switch in the scope.
        /// The scope must be a Method or Constructor.
        /// </summary>
        /// <param name="name">Switch name.</param>
        /// <returns>The newly defined SwitchSymbol.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown if the scope is not a Method or Constructor.</exception>
        public virtual SwitchSymbol AddSwitch(string name)
        {
            if (!(this is MethodSymbolBase)) throw new InvalidOperationException("Only Methods can define switches.");
            SwitchSymbol sw = new SwitchSymbol(name);
            Add(name, sw);
            return sw;
        }

        /// <summary>
        /// Defines a new variable in the scope.
        /// The scope must be a Method or a Block.
        /// </summary>
        /// <param name="name">Variable name.</param>
        /// <param name="type">Variable type.</param>
        /// <returns>The newly defined VariableSymbol.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown if the scope is not Method or Block.</exception>
        public virtual VariableSymbol AddVariable(string name, TypeSymbol type)
        {
            //TODO: remove Inspect as legal container. We should use blocks for this.
            if (!((this is MethodSymbolBase) || (this is BlockSymbol) || (this is InspectSymbol))) 
                throw new InvalidOperationException(string.Format("Only Methods and Blocks can define inspects, current scope is {0}", this.GetType().Name));

            VariableSymbol var = new VariableSymbol(name, type);
            Add(name, var);
            return var;
        }

        /// <summary>
        /// Returns a new Inspect scope of the specified field.
        /// The scope must be a Method or a Block.
        /// </summary>
        /// <param name="field">Field to be inspected.</param>
        /// <returns>The new Inspect scope of the specified field.</returns>
        public virtual InspectSymbol AddInspect(FieldSymbol field)
        {
            if (!((this is MethodSymbolBase) || (this is BlockSymbol) || (this is InspectSymbol)))
                throw new InvalidOperationException(string.Format("Only Methods and Blocks can define inspects, current scope is {0}", this.GetType().Name));

            InspectSymbol iSym = new InspectSymbol(field);
            iSym.Scope = this;
            return iSym;
        }

        /// <summary>
        /// Returns a new Inspet scope of the specified variable.
        /// The scope must be a Method or a Block.
        /// </summary>
        /// <param name="var">Variable to be inspected.</param>
        /// <returns>The new Inspect scope of the specified variable.</returns>
        public virtual InspectSymbol AddInspect(VariableSymbol var)
        {
            InspectSymbol inspect = new InspectSymbol(var);
            inspect.Scope = this;
            return inspect;
        }

        /// <summary>
        /// Resolves the type with specified name.
        /// </summary>
        /// <param name="name">Name of type to resolve.</param>
        /// <returns>The resolved type or null if type does not exist.</returns>
        public TypeSymbol ResolveType(string name)
        {
            string n = name.ToLower();
            switch (n)
            {
                case "void": name = "notype"; break;
                case "char": name = "character"; break;
                case "int32": name = "integer"; break;
                case "double": name = "long"; break;
                case "int16": name = "short"; break;
                case "single": name = "real"; break;
            }
            return this.Resolve(name) as TypeSymbol;
        }

        /// <summary>
        /// Resolves the field with specified name.
        /// </summary>
        /// <param name="name">Name of field to resolve.</param>
        /// <returns>The resolved field or null if field does not exist.</returns>
        public FieldSymbol ResolveField(string name)
        {
            return this.Resolve(name) as FieldSymbol;
        }

        /// <summary>
        /// Resolves the method with the specified name.
        /// </summary>
        /// <param name="name">Name of the method to resolve.</param>
        /// <returns>The resolved method or null if method does not exist.</returns>
        public MethodSymbol ResolveMethod(string name)
        {
            return this.Resolve(name) as MethodSymbol;
        }

        /// <summary>
        /// Resolves the variable with the specified name.
        /// </summary>
        /// <param name="name">Name of the variable to resolve.</param>
        /// <returns>The resolved variable or null if variable does not exist.</returns>
        public VariableSymbol ResolveVariable(string name)
        {
            return this.Resolve(name) as VariableSymbol;
        }


        /// <summary>
        /// Resolves the argument with the specified name.
        /// </summary>
        /// <param name="name">Name of the argument to resolve.</param>
        /// <returns>The resolved argument or null if argument does not exist.</returns>
        public ArgumentSymbol ResolveArgument(string name)
        {
            return this.Resolve(name) as ArgumentSymbol;
        }

        /// <summary>
        /// Resolves the label with the specified name.
        /// </summary>
        /// <param name="name">Name of the label to resolve.</param>
        /// <returns>The resolved label or null if label does not exist.</returns>
        public LabelSymbol ResolveLabel(string name)
        {
            return this.Resolve(name) as LabelSymbol;
        }

        /// <summary>
        /// Resolves the switch with the specified name.
        /// </summary>
        /// <param name="name">Name of the switch to resolve.</param>
        /// <returns>The resolved switch or null if label does not exist.</returns>
        public SwitchSymbol ResolveSwitch(string name)
        {
            return this.Resolve(name) as SwitchSymbol;
        }

        /// <summary>
        /// Searches for the named symbol and returns the enclosing scope if the scope is an InspectScope.
        /// </summary>
        /// <param name="s">Symbol for which to return the enclosing Inspect scope.</param>
        /// <returns>The enclosing Inspect Scope or null.</returns>
        public virtual InspectSymbol GetInspectForSymbol(Symbol s)
        {
            if (this.Scope != null) return this.Scope.GetInspectForSymbol(s);
            return null;
        }

        //public virtual BlockSymbol AddBlock(string name)
        //{
        //    BlockSymbol block = new BlockSymbol(name, this);
        //    Add(name, block);
        //    block.Scope = this;
        //    return block;
        //}







 

        //public bool IsCurrentMethod(Symbol sym)
        //{
        //    if (this == sym) return true;
        //    if (this is TypeSymbol) return false;
        //    return this.Scope.IsCurrentMethod(sym);
        //}





        //public override string ToString()
        //{
        //    StringBuilder sb = new StringBuilder();
        //    string n = Name;
        //    if (n == null) n = "-nil-";
        //    sb.AppendLine("Scope(" + n + "):");

        //    List<Symbol> innerScopes = new List<Symbol>();
        //    foreach (Symbol s in Members.Values)
        //    {
        //        sb.Append('+');
        //        if (s is ScopeSymbol)
        //        {
        //            sb.Append(s.Name);
        //            innerScopes.Add(s);
        //        }
        //        else sb.Append(s);
        //    }
        //    sb.AppendLine();
        //    foreach (Symbol s in innerScopes) if(s is TypeSymbol) sb.Append(s.ToString());
        //    sb.AppendLine();

        //    return sb.ToString();
        //}


    }
}
